/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.XPath;
using System.Xml;

namespace DaveSexton.DocProject
{
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
	public sealed class CommentsMember : IEnumerable<XmlElement>
	{
		#region Public Properties
		public string Name
		{
			get
			{
				return name;
			}
		}

		public string this[string section]
		{
			get
			{
				XmlElement element = GetSection(section, false);

				if (element == null)
					return string.Empty;
				else
					return element.InnerXml;
			}
			set
			{
				XmlElement element = GetSection(section, value != null);

				if (value == null)
				{
					if (element != null)
						// NOTE: Do not remove elements since they may be cached by the owner of this class.
						element.InnerXml = string.Empty;
				}
				else
				{
					if (!string.Equals(element.InnerXml, value, StringComparison.Ordinal))
					{
						element.InnerXml = value;

						OnSectionChanged(new CommentsMemberSectionEventArgs(section));
					}
				}
			}
		}

		public string Summary
		{
			get
			{
				return this["summary"];
			}
			set
			{
				this["summary"] = value;
			}
		}

		public string Remarks
		{
			get
			{
				return this["remarks"];
			}
			set
			{
				this["remarks"] = value;
			}
		}

		public CommentsDocument Document
		{
			get
			{
				return document;
			}
		}
		#endregion

		#region Private / Protected
		private readonly CommentsDocument document;
		private readonly XmlElement member;
		private readonly string name;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="CommentsMember" /> class.
		/// </summary>
		internal CommentsMember(CommentsDocument document, XmlElement member, string name)
		{
			this.document = document;
			this.name = name;
			this.member = member;
		}
		#endregion

		#region Methods
		public XmlElement GetSection(string section, bool createIfMissing)
		{
			XmlElement element = member[section];

			if (element == null && createIfMissing)
				element = CreateSection(section);

			return element;
		}

		public XmlElement CreateSection(string section)
		{
			XmlElement element = (XmlElement) member.AppendChild(member.OwnerDocument.CreateElement(section));

			OnSectionChanged(new CommentsMemberSectionEventArgs(section));

			return element;
		}

		public IDictionary<string, string> GetAttributes(string section)
		{
			XmlElement element = GetSection(section, false);

			if (element == null)
				return null;
			else
			{
				Dictionary<string, string> attributes = new Dictionary<string, string>(8);

				foreach (XmlAttribute attribute in element.Attributes)
					attributes.Add(attribute.Name, attribute.Value);

				return attributes;
			}
		}

		public string GetAttribute(string section, string attribute)
		{
			XmlElement element = GetSection(section, false);

			if (element == null)
				return null;
			else
			{
				XmlAttribute attr = element.Attributes[attribute];

				if (attr == null)
					return null;
				else
					return attr.Value;
			}
		}

		public void SetAttribute(string section, string attribute, string value)
		{
			XmlElement element = GetSection(section, true);

			XmlAttribute attr = element.Attributes[attribute];

			if (value == null)
			{
				if (attr != null)
					element.Attributes.Remove(attr);
			}
			else
			{
				if (attr == null)
					attr = element.Attributes.Append(element.OwnerDocument.CreateAttribute(attribute));

				attr.Value = value;
			}
		}

		public void NotifySectionChanged(string section)
		{
			OnSectionChanged(new CommentsMemberSectionEventArgs(section));
		}
		#endregion

		#region Events
		private readonly object SectionChangedEventLock = new object();
		private EventHandler<CommentsMemberSectionEventArgs> SectionChangedEvent;

		/// <summary>
		/// Event raised when the value of a section is changed.
		/// </summary>
		public event EventHandler<CommentsMemberSectionEventArgs> SectionChanged
		{
			add
			{
				lock (SectionChangedEventLock)
				{
					SectionChangedEvent += value;
				}
			}
			remove
			{
				lock (SectionChangedEventLock)
				{
					SectionChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SectionChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="CommentsMemberSectionEventArgs" /> object that provides the arguments for the event.</param>
		private void OnSectionChanged(CommentsMemberSectionEventArgs e)
		{
			EventHandler<CommentsMemberSectionEventArgs> handler = null;

			lock (SectionChangedEventLock)
			{
				handler = SectionChangedEvent;
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region IEnumerable<XmlElement> Members
		public IEnumerator<XmlElement> GetEnumerator()
		{
			foreach (XmlElement element in member.SelectNodes("*"))
				yield return element;
		}
		#endregion

		#region IEnumerable Members
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
		#endregion
	}

	/// <summary>
	/// Provides arguments for the <see cref="CommentsMember.SectionChanged"/> event.
	/// </summary>
	[Serializable]
	public class CommentsMemberSectionEventArgs : EventArgs
	{
		#region Public Properties
		public string Section
		{
			get
			{
				return section;
			}
		}
		#endregion

		#region Private / Protected
		private readonly string section;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="CommentsMemberSectionEventArgs" /> class.
		/// </summary>
		public CommentsMemberSectionEventArgs(string section)
		{
			if (string.IsNullOrEmpty(section))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "section");

			this.section = section;
		}
		#endregion
	}
}
